package com.hqd.ch03.v27;

import com.hqd.ch03.v27.aware.*;
import com.hqd.ch03.v27.beans.BeanWrapper;
import com.hqd.ch03.v27.beans.BeanWrapperImpl;
import com.hqd.ch03.v27.beans.PropertyValues;
import com.hqd.ch03.v27.beans.TypeConverter;
import com.hqd.ch03.v27.config.*;
import com.hqd.ch03.v27.factory.BeanFactory;
import com.hqd.ch03.v27.factory.ConfigurableListableBeanFactory;
import com.hqd.ch03.v27.factory.FactoryBean;
import com.hqd.ch03.v27.factory.support.BeanDefinitionValueResolver;
import com.hqd.ch03.v27.io.ResourceLoader;
import com.hqd.ch03.v27.io.support.DefaultResourceLoader;
import com.hqd.ch03.v27.listener.BeanFactoryEventMulticaster;
import com.hqd.ch03.v27.listener.BeanFactoryEventPublisher;
import com.hqd.ch03.v27.listener.BeanFactoryListener;
import com.hqd.ch03.v27.listener.event.BeanFactoryEvent;
import com.hqd.ch03.v27.listener.support.SimpleBeanFactoryEventMulticaster;
import com.hqd.ch03.v27.registry.BeanDefinitionRegistry;
import com.hqd.ch03.v27.registry.support.DefaultSingletonBeanRegistry;
import com.hqd.ch03.v27.registry.support.SimpleBeanDefinitionRegistry;
import com.hqd.ch03.v27.spel.BeanExpressionResolver;
import com.hqd.ch03.v27.spel.express.BeanExpressionContext;
import com.hqd.ch03.v27.spel.express.StandardBeanExpressionResolver;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;

import java.beans.PropertyEditor;
import java.lang.reflect.Method;
import java.util.*;

public abstract class AbstractSpringImitation extends DefaultSingletonBeanRegistry implements ConfigurableListableBeanFactory, BeanFactoryEventPublisher {
    /**
     * 自定义类型转换器
     */
    private final Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();
    private final StringValueResolver embeddedValueResolver = new EmbeddedValueResolver(this);
    /**
     * 事件广播器
     */
    protected BeanFactoryEventMulticaster multicaster = new SimpleBeanFactoryEventMulticaster(this);
    protected BeanDefinitionRegistry beanDefinitionRegistry = new SimpleBeanDefinitionRegistry();
    /**
     * 单例缓存
     */
    protected Map<String, Scope> scopeCache = new HashMap<>();
    /**
     * 资源加载器
     */
    protected ResourceLoader resourceLoader = new DefaultResourceLoader();
    protected BeanExpressionResolver beanExpressionResolver = new StandardBeanExpressionResolver();
    private TypeConverter typeConverter;
    /**
     * bean初始化回调接口
     */
    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    public static boolean isFactoryDereference(String name) {
        return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
    }

    @Override
    public void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass) {
        customEditors.put(requiredType, propertyEditorClass);
    }

    @Override
    public void publishEvent(BeanFactoryEvent event) {
        doPublishEvent(event);
    }

    protected void doPublishEvent(BeanFactoryEvent event) {
        multicaster.multicastEvent(event);
    }

    @Override
    public void addBeanFactoryListener(BeanFactoryListener<?> listener) {
        multicaster.addBeanFactoryListener(listener);
    }

    @Override
    public BeanExpressionResolver getBeanExpressionResolver() {
        return this.beanExpressionResolver;
    }

    @Override
    public boolean containsBean(String name) {
        String beanName = transformedBeanName(name);
        if (containsSingleton(beanName) || containsBeanDefinition(beanName)) {
            return true;
        }
        return false;
    }

    /**
     * 处理传入的name，去除&开头
     *
     * @param name
     * @return
     */
    protected String transformedBeanName(String name) {
        if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
            return name;
        }
        String beanName = name;
        do {
            beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
        }
        while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
        return beanName;
    }

    /**
     * 区分正常bean还是FactoryBean
     *
     * @param obj
     * @param name
     * @param beanName
     * @param bd
     * @return
     */
    protected Object getObjectForBeanInstance(Object obj, String name, String beanName, BeanDefinition bd) {
        if (isFactoryDereference(name)) {
            if (!(obj instanceof FactoryBean)) {
                throw new RuntimeException(String.format("%s 未实现FactoryBean接口", beanName));
            }
            return obj;
        }
        if (obj instanceof FactoryBean) {
            return ((FactoryBean) obj).getObject();
        }
        return obj;
    }

    private void populateBean(BeanWrapper beanWrapper, String beanName, BeanDefinition bd, PropertyValues pvs) {
        try {
            TypeConverter converter = getCustomTypeConverter();
            if (converter == null) {
                converter = beanWrapper;
            }
            BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, bd, converter);
            PropertyValues properties = pvs;
            if (properties != null) {
                for (PropertyValue pv : properties) {
                    Object originalValue = pv.getValue();
                    String propertyName = pv.getName();
                    Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
                    beanWrapper.setPropertyValue(propertyName, resolvedValue);
                }
            }
        } catch (Exception e) {
            throw e;
        }

    }


    private TypeConverter getCustomTypeConverter() {
        return this.typeConverter;
    }

    private Object createBean(BeanDefinition bd, String beanName) {
        try {
            Object instance = null;
            if (bd.isSingleton()) {//单例
                instance = getSingleton(beanName);
                if (instance == null) {
                    instance = doCreate(bd, beanName);
                }
                addSingleton(beanName, instance);
            } else if (bd.isPrototype()) {//多例
                instance = doCreate(bd, beanName);
            } else {//自定义作用域
                String scopeName = bd.getScope();
                Scope scope = scopeCache.get(scopeName);
                if (scope == null) {
                    throw new RuntimeException(String.format("[%s]未注册scope", scopeName));
                }
                instance = scope.get(beanName, () -> doCreate(bd, beanName));
            }
            return instance;

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 设置Aware接口
     *
     * @param beanName
     * @param bean
     */
    private void invokeAwareMethods(String beanName, Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof EmbeddedValueResolverAware) {
                ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
            }
            if (bean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) bean).setResourceLoader(resourceLoader);
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(this);
            }
            if (bean instanceof BeanFactoryEventPublisherAware) {
                ((BeanFactoryEventPublisherAware) bean).setBeanFactoryEventPublisher(this);
            }
            if (bean instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(Thread.currentThread().getContextClassLoader());
            }
        }
    }

    protected Object doCreate(BeanDefinition bd, String beanName) {
        String beanClassName = bd.getBeanClass();
        try {
            Class<?> beanClass = Class.forName(beanClassName);
            Object instance = beanClass.getConstructor().newInstance();
            BeanWrapper beanWrapper = new BeanWrapperImpl(instance, beanClass);
            initBeanWrapper(beanWrapper);
            addSingletonFactory(beanName, () -> beanWrapper.getWrappedInstance());
            /**
             * 执行init有可能对象发生变化
             */
            Object exposedObject = instance;
            PropertyValues pvs = (bd.hasPropertyValues() ? bd.getPropertyValues() : null);
            populateBean(beanWrapper, beanName, bd, pvs);
            exposedObject = initializeBean(beanName, exposedObject, bd);
            return exposedObject;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    protected Object initializeBean(String beanName, Object bean, BeanDefinition bd) {
        if (bean != null) {
            invokeAwareMethods(beanName, bean);
        }
        Object wrappedBean = bean;
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        //调用init方法
        invokeInitMethods(wrappedBean, bd);
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        return wrappedBean;
    }

    /**
     * 执行init方法
     *
     * @param wrappedBean
     * @param bd
     */
    protected void invokeInitMethods(Object wrappedBean, BeanDefinition bd) {
        if (wrappedBean instanceof InitializingBean) {
            try {
                ((InitializingBean) wrappedBean).afterPropertiesSet();
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }
        String initMethodName = bd.getInitMethodName();
        if (StringUtils.isBlank(initMethodName)) {
            return;
        }
        try {
            Method initMethod = wrappedBean.getClass().getDeclaredMethod(initMethodName);
            initMethod.invoke(wrappedBean);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 执行BeanPostProcessor前置方法
     *
     * @param existingBean
     * @param beanName
     * @return
     */
    protected Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) {
        Object result = existingBean;
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
            Object current = processor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    /**
     * 执行BeanPostProcessor后置方法
     *
     * @param existingBean
     * @param beanName
     * @return
     */
    protected Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) {
        Object result = existingBean;
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
            Object current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    public List<BeanPostProcessor> getBeanPostProcessors() {
        String[] beanNamesForType = getBeanNamesForType(BeanPostProcessor.class);
        if (ArrayUtils.isNotEmpty(beanNamesForType)) {
            for (String name : beanNamesForType) {
                BeanPostProcessor bean = (BeanPostProcessor) this.getBean(name);
                if (!beanPostProcessors.contains(bean)) {
                    this.beanPostProcessors.add(bean);
                }
            }
        }
        return beanPostProcessors;
    }

    @Override
    public void addBeanPostProcessor(BeanPostProcessor postProcessor) {
        if (postProcessor != null) {
            synchronized (beanPostProcessors) {
                beanPostProcessors.remove(postProcessor);
                beanPostProcessors.add(postProcessor);
            }
        }
    }

    protected void initBeanWrapper(BeanWrapper bw) {
        /**
         * 注册自定义类型转换器
         *
         */
        registerCustomEditors(bw);
    }

    /**
     * 是否应该区分全局转换器和局部转换器
     * 全局为Factory级别，局部为Bean级别
     *
     * @param bw
     */
    protected void registerCustomEditors(BeanWrapper bw) {
        if (MapUtils.isNotEmpty(customEditors)) {
            customEditors.forEach((key, val) -> {
                try {
                    bw.registerCustomEditor(key, val.getConstructor().newInstance());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

    @Override
    public void registerScope(String scopeName, Scope scope) {
        if (StringUtils.isNotBlank(scopeName) && scope != null) {
            scopeCache.put(scopeName, scope);
        }
    }

    @Override
    public <T> T getBean(String name, Class<T> clazz) {
        return (T) doGetBean(name);
    }

    @Override
    public Object getBean(String name) {
        return doGetBean(name);
    }

    protected Object doGetBean(String name) {
        String beanName = transformedBeanName(name);
        BeanDefinition bd = beanDefinitionRegistry.getBeanDefinition(beanName);
        Object bean = createBean(bd, beanName);
        bean = getObjectForBeanInstance(bean, name, beanName, bd);
        return bean;
    }

    @Override
    public boolean isPrototype(String targetBeanName) {
        return this.beanDefinitionRegistry.getBeanDefinition(targetBeanName).isPrototype();
    }

    @Override
    public boolean isSingleton(String targetBeanName) {
        return this.beanDefinitionRegistry.getBeanDefinition(targetBeanName).isSingleton();
    }

    @Override
    public Class<?> getType(String name) {
        String beanName = transformedBeanName(name);

        Object beanInstance = getSingleton(beanName);
        if (beanInstance != null) {
            if (beanInstance instanceof FactoryBean && !isFactoryDereference(name)) {
                return ((FactoryBean<?>) beanInstance).getObjectType();
            } else {
                return beanInstance.getClass();
            }
        }
        BeanDefinition bd = beanDefinitionRegistry.getBeanDefinition(beanName);
        if (bd != null && !isFactoryDereference(name)) {
            try {
                return Class.forName(bd.getBeanClass());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        } else {
            return null;
        }

    }

    public String[] getBeanNamesForType(Class<?> beanClass) {
        String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
        List<String> listenerNames = new ArrayList<>(beanDefinitionNames.length);
        Arrays.stream(beanDefinitionNames).forEach(beanName -> {
            BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanName);
            try {
                if (ClassUtils.isAssignable(ClassUtils.getClass(beanDefinition.getBeanClass()), beanClass)) {
                    listenerNames.add(beanName);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        });
        return listenerNames.toArray(new String[]{});

    }

    @Override
    public TypeConverter getTypeConverter() {
        return getCustomTypeConverter();
    }

    @Override
    public void setTypeConverter(TypeConverter typeConverter) {
        this.typeConverter = typeConverter;
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return this.beanDefinitionRegistry.containsBeanDefinition(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return this.beanDefinitionRegistry.getBeanDefinition(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return this.beanDefinitionRegistry.getBeanDefinitionNames().length;
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionRegistry.getBeanDefinitionNames();
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    public Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) {
        if (this.beanExpressionResolver == null) {
            return value;
        }
        return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this));
    }
}
